iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
Software Development

從 Vibe Coding 到可維護專案:用 Hermes Agent 重讀 RoomRush Android 專題系列 第 15 篇

Day 15 | Repository 從哪裡生出來?我第一次看懂 AppContainer 的用途

  • 分享至 

  • xImage
  •  

前言:Activity / ViewModel 如何取得 Repository 與資料來源?

前一天我們搞懂了資料庫的讀寫與預載機制,今天我們要繼續追資料主線。但這一次視角不太一樣,我們要來聊聊「依賴注入(Dependency Injection)」。

聽起來名詞很深奧,但它要解決的問題其實非常生活化:
前幾天我們看到,不管是查詢頁還是詳細頁的 ViewModel,全部都需要 ClassroomScheduleRepository 來撈資料。但這個 Repository 該由誰建立?又要怎麼交到各個 ViewModel 手上?

於是,專案中出現了 EmptyRoomFinderApp.kt 與 AppContainer.kt,它們就像是整支 App 的「全域共享工具箱」,這就是我們今天要深入的主題。


一開始我認為

其實一開始,我不太懂什麼叫做「依賴注入(Dependency Injection)」,所以對今天要討論的這兩個檔案其實很不熟,後來實際了解後,我們首先可以拆解兩個問題:

  • 什麼是「依賴(Dependency)」?

    當 A 類別要完成工作,必須用到 B 類別時,B 就是 A 的依賴。

    例如:RoomDetailViewModel 想要查課表,必須用到 ClassroomScheduleRepository,這時候 Repository 就是 ViewModel 的依賴。

  • 什麼是「注入(Injection)」?

    注入就是 「從外面把東西傳進來給它」,而不是讓它自己動手去做。

想像你今天開了一間早餐店(ViewModel),店裡要賣美式咖啡:

  1. 沒有依賴注入(自己做):

    早餐店老闆為了賣咖啡,自己去買咖啡豆、買烘焙機、甚至自己蓋了一座咖啡豆烘焙廠。
    → 缺點: 早餐店變得越來越肥大,萬一以後想換成別家供應商的咖啡豆,整間店都得打掉重改。

  2. 有依賴注入(外面送進來):
    早餐店老闆在請人送咖啡豆(Repository)進來,因此每天早上由總部物流中心(AppContainer)統一配送烘好的咖啡豆進店裡。
    → 優點: 早餐店老闆只需要專心泡咖啡(處理 UI 畫面邏輯),完全不用管咖啡豆是怎麼種出來的。

那為什麼我們需要依賴注入?

主要有以下三個原因:

  1. 職責單純(解耦):類別只專注做自己的事,不再負責「生產」其他工具。
  2. 共用資源不浪費:由最外層的 AppContainer 統一建立一份資料庫與 Repository,所有頁面共用同一個實例,避免記憶體浪費。
  3. 極度方便寫測試:寫單元測試時,我們可以直接傳入一個「假資料的 Mock Repository」,完全不需要開啟真實的資料庫。

實際讀完後,EmptyRoomFinderApp.kt 與 AppContainer 負責什麼

和 Hermes Agent 一起重讀檔案後,EmptyRoomFinderApp.kt 是 RoomRush 自訂的 Application 類別。它是整個 App 啟動時的全域初始化入口,讓其他 Activity 可以透過 AppContainer 取得共用依賴,例如 Repository。

class EmptyRoomFinderApp : Application() {

    // AppContainer 實例:用於依賴注入 (Dependency Injection)
    // 專案中的其他類別 (如 Activity) 會透過這個實例來獲取所需的依賴 (如 Repository)
    lateinit var appContainer: AppContainer

    override fun onCreate() {
        super.onCreate()
        // 在 App 啟動時立即初始化 AppContainer
        appContainer = AppContainer(this)
    }
}
  • 關鍵細節 1:Application Context 的安全性
    這裡傳入 AppContainer(this) 的 this 代表整個應用程式層級的 Context。它從 App 開啟到結束都存在,能確保後續建立資料庫時完全不會造成記憶體洩漏(Memory Leak)。

  • 關鍵細節 2:lateinit 的延遲初始化
    因為 Android 的 Application 實例是由系統建立的,無法直接在宣告時拿到 Context,所以透過 lateinit var 宣告,並在系統回呼的 onCreate() 中第一時間完成初始化。

AppContainer.kt 是 RoomRush 的簡單依賴容器。它負責集中建立和保存 App 共用的依賴物件,目前主要是 ClassroomScheduleRepository。

class AppContainer(context: Context) {

    // ClassroomScheduleRepository 實例:會被建立一次並在多個頁面間共享
    // 利用 "by lazy" (延遲初始化):代表這個 Repository 只有在第一次被存取時才會真正建立
    val classroomScheduleRepository: ClassroomScheduleRepository by lazy {
        // 1. 取得資料庫實例 (AppDatabase)
        val db = AppDatabase.getInstance(context)
        // 2. 將資料庫的 DAO 傳入 Repository 並回傳
        ClassroomScheduleRepository(db.classroomScheduleDao())
    }
}
  • 關鍵細節 1:by lazy 的效能優勢
    這段程式碼最重點在於 by lazy。它確保了兩件事:

    1. 不拖慢啟動速度: App 一打開時不會立刻去建資料庫,而是等到某個畫面第一次真正呼叫 classroomScheduleRepository 時才建立。
    2. 單例共享(Singleton): 一旦建立完成,實例就會被快取下來,之後不管是查詢頁還是詳細頁來拿,拿到的都是同一個 Repository 實例,節省記憶體資源。
  • 關鍵細節 2:清晰的依賴組裝鏈
    清楚展示了物件的生成順序:Context → AppDatabase → DAO → Repository

    ViewModel 只要向 Container 要 Repository 就好,完全不需要知道背後這些複雜的步驟。


它接在哪一條流程上

1. App 啟動後,Activity 怎麼拿到 Repository?

AndroidManifest.xml
註冊 EmptyRoomFinderApp
    ↓
App 啟動
    ↓
EmptyRoomFinderApp.onCreate()
    ↓
建立 AppContainer
    ↓
Activity
    ↓
從 AppContainer 取得 Repository
    ↓
建立 / 取得 ViewModel
    ↓
將 Repository 提供給 ViewModel
    ↓
ViewModel 使用 Repository

2. AppContainer 裡面,Repository 是怎麼建立的?

EmptyRoomFinderApp
    ↓
建立 AppContainer
    ↓
AppContainer
    ↓
使用 Context 建立 / 取得 AppDatabase
    ↓
從 Database 取得 ClassroomScheduleDao
    ↓
用 DAO 建立 Repository
    ↓
Repository

Hermes Agent 幫我檢查出的重點

EmptyRoomFinderApp 是 RoomRush 自訂的 Application 類別。

  • 它會在 App 啟動時被建立一次,不是畫面,而是全域初始化入口。
  • 它在 onCreate() 裡建立 AppContainer(this),其中 this 是 Application Context。之後 Activity 可以透過 (application as EmptyRoomFinderApp).appContainer 取得共用依賴,例如 ClassroomScheduleRepository。
  • 它和 AppDatabase 的關係不是直接執行 CSV 預載,而是先建立 AppContainer;AppContainer 下一步才會用 Context 建立 Database 和 Repository。

AppContainer 是 RoomRush 的簡單依賴容器,由 EmptyRoomFinderApp 在 App 啟動時建立。

  • 它接收 Application Context,並在第一次有人使用 classroomScheduleRepository 時,透過 AppDatabase.getInstance(context) 取得資料庫實例,再呼叫 db.classroomScheduleDao() 取得 DAO,最後把 DAO 傳入 ClassroomScheduleRepository。
  • Activity 可以透過 (application as EmptyRoomFinderApp).appContainer.classroomScheduleRepository 取得這個 Repository,並傳給 ViewModelFactory。

小結

讀完 EmptyRoomFinderApp 和 AppContainer 後,我把 RoomRush 的資料層建立流程補完整了。

EmptyRoomFinderApp 只負責在 App 啟動時準備共用依賴,這個檔案的重點很短:建立 AppContainer。雖然程式碼只有幾行,但它讓 Activity 可以從全域的 Application 拿到 appContainer,再取得 Repository。這是一種簡單的依賴注入方式,也讓資料層物件不用在每個 Activity 裡重複建立。

而 AppContainer 才是真正建立資料層依賴的地方。它先用 Context 取得 AppDatabase,再從 Database 取得 DAO,最後建立 Repository。這個檔案雖然只有幾行,但它讓我理解「依賴注入」的簡化版本:不是每個 Activity 自己去建立資料庫或 Repository,而是由 AppContainer 統一建立,再提供給需要的地方。這讓資料層物件的來源更集中,也比較容易維護。

各用一句話總結兩個檔案:

EmptyRoomFinderApp 是 App 啟動時的全域初始化入口,負責建立 AppContainer,讓 Activity / ViewModel 可以取得共用的 Repository。

AppContainer 用 Context 取得 AppDatabase,再從 Database 取得 DAO,最後建立 ClassroomScheduleRepository 給 Activity / ViewModel 使用。


下一篇預告:從 CSV 到畫面:RoomRush 的資料流我終於串起來了

了解完 EmptyRoomFinderApp.kt 及 AppContainer.kt 是如何作為全域工具箱之後,我們的資料來源主線就告了一段落。

下一篇,我們要來總結資料來源主線,透過這次的總結,我們可以更看懂 RoomRush 的空教室資料是怎麼來的,並完成第二階段的「資料來源總圖」,看原始的 CSV 檔案,是如何一步步被解析、存入 Room 資料庫、組裝進 Repository,並最終轉化為 ViewModel 中的 emptyRooms。


上一篇
Day 14 | CSV 檔案是怎麼變成 App 裡可以查的課表資料?
下一篇
Day 16 | 從 CSV 到畫面:RoomRush 的資料流我終於串起來了
系列文
從 Vibe Coding 到可維護專案:用 Hermes Agent 重讀 RoomRush Android 專題 共 23 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言